using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
using Object = System.Object;


public class WebImageManager : MonoBehaviour
{
    [Serializable]
    public class ImageCache
    {
        public static long ImageCacheSize = 0;
        
        public string url;
        public Texture2D image;
        public int size;
        public LinkedList<WeakReference> holders;

        public bool isLoading;
        //public LinkedList<WeakReference<Action<Texture2D, string>>> successActions;
        //public LinkedList<WeakReference<Action<string>>> failActions;
        public LinkedList<Action<Texture2D, string>> successActions;
        public LinkedList<Action<string>> failActions;

        public float unusedTime = float.MaxValue;

        public ImageCache(string url)
        {
            this.url = url;
            this.size = 0;
            this.holders = new LinkedList<WeakReference>();

            this.isLoading = false;
            //this.successActions = new LinkedList<WeakReference<Action<Texture2D, string>>>();
            //this.failActions = new LinkedList<WeakReference<Action<string>>>();
            this.successActions = new LinkedList<Action<Texture2D, string>>();
            this.failActions = new LinkedList<Action<string>>();
        }

        public void ClearEmptyHolder()
        {
            var node = holders.First;
            while (node != null)
            {
                var next = node.Next;
                if (node.Value == null || !node.Value.IsAlive)
                    holders.Remove(node);
                node = next;
            }
        }

        public void AddHolder(Object obj)
        {
            if (holders == null)
            {
                holders = new LinkedList<WeakReference>();
            }

            WeakReference wr = new WeakReference(obj);
            holders.AddLast(wr);
            unusedTime = float.MaxValue;
            ClearEmptyHolder();
        }
        
        public void RemoveHolder(Object obj)
        {
            if (holders == null) return;
            foreach (var holder in holders)
            {
                if (holder.IsAlive && (Object)holder.Target == obj)
                {
                    holders.Remove(holder);
                    break;
                }
            }
            ClearEmptyHolder();
            
            if (holders.Count == 0)
            {
                // 没有对象持有了
                unusedTime = Time.time;
            }
        }

        public bool TryGetHolder(Object obj,out WeakReference wr)
        {
            foreach (var holder in holders)
            {
                if (holder.IsAlive && (Object)holder.Target == obj)
                {
                    wr = holder;
                    return true;
                }
            }
            wr = null;
            return false;
        }
        
        public void RemoveSuccessAction(Action<Texture2D, string> action)
        {
            if(successActions == null) return;
            foreach (var successAction in successActions)
            {
                //Action<Texture2D, string> sa;
                //if (successAction.TryGetTarget(out sa))
                //{
                //    if (sa == action)
                //    {
                //        successActions.Remove(successAction);
                //        break;
                //    }
                //}
                if (successAction == action)
                {
                    successActions.Remove(successAction);
                    break;
                }
            }
        }

        public void RemoveFailAction(Action<string> action)
        {
            if(failActions == null) return;
            foreach (var failAction in failActions)
            {
                //Action<string> fa;
                //if (failAction.TryGetTarget(out fa))
                //{
                //    if (fa == action)
                //    {
                //        failActions.Remove(failAction);
                //        break;
                //    }
                //}
                if (failAction == action)
                {
                    failActions.Remove(failAction);
                    break;
                }
            }
        }
        
        public void Success(Texture2D tex)
        {
            //Debug.Assert(tex != null);
            image = tex;
            //size = tex.width * tex.height;
            //ImageCacheSize += size;
            if (successActions != null)
            {
                foreach (var successAction in successActions)
                {
                    //Action<Texture2D, string> action;
                    //if (successAction.TryGetTarget(out action))
                    //{
                    //    action?.Invoke(tex,url);
                    //}
                    //这里TryGetTarget获取不到action去除WeakReference直接获取
                    successAction?.Invoke(tex, url);
                }
                successActions.Clear();
            }

            if (failActions != null)
            {
                failActions.Clear();
            }
        }

        public void Fail()
        {
            if (failActions!=null)
            {
                foreach (var failAction in failActions)
                {
                    //Action<string> action;
                    //if (failAction.TryGetTarget(out action))
                    //{
                    //    action?.Invoke(url);   
                    //}
                    failAction?.Invoke(url);
                }
                
                failActions.Clear();
            }

            if (successActions != null)
            {
                successActions.Clear();
            }
        }


        public void Dispose()
        {
            if (image)
            {
                //ImageCacheSize -= size;
                Destroy(image);
                image = null;
            }


            holders.Clear();
            holders = null;
            successActions.Clear();
            successActions = null;
            failActions.Clear();
            failActions = null;
        }
        
    }
    
    #region INSTANCE
    private static WebImageManager instance;
    public static WebImageManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<WebImageManager>();
                if (instance == null)
                {
                    var obj = new GameObject("WebImageManager");
                    var manager = GameObject.Find("Manager");
                    if (manager != null)
                    {
                        obj.transform.SetParent(manager.transform, false);
                    }
                    else
                    {
                        DontDestroyOnLoad(obj);
                    }
                    
                    instance = obj.AddComponent<WebImageManager>();
                }
            }
            return instance;
        }
    }
    #endregion

    private const string IMAGE_CACHE_DIC_NAME = "WebImageCache";
    
    private Dictionary<string, ImageCache> ImageCacheDic = new Dictionary<string, ImageCache>(100);

// #if UNITY_EDITOR
//     public Dictionary<string, ImageCache> ImageCacheDicGet
//     {
//         get { return ImageCacheDic; }
//     }
// #endif
    
    // 每隔一段时间检测没有被引用的图片缓存
    public float CheckTime = 5f;
    private float _checkTime = 0;
    // 未被引用的图片持有时间
    public float ImageHoldTime = 5f;
    // 每次最多卸载的图片缓存
    public int MaxUnloadCount = 10;
    
    // 最大缓存大小
    private const int MaxCacheSize = 1024 * 2048;
    
    private void Update()
    {
        //if (ImageCache.ImageCacheSize > MaxCacheSize)
        {
            _checkTime += Time.deltaTime;
            if (_checkTime > CheckTime)
            {
                foreach (var imagePair in ImageCacheDic)
                {
                    imagePair.Value.ClearEmptyHolder();
                }
                UnLoadUnusedImage(MaxUnloadCount,ImageHoldTime);
                _checkTime = 0;
            }
        }
    }
    List<string> unloadKeys = new List<string>(50);

    private void UnLoadUnusedImage(int count,float holdTime)
    {
        int c = 0;

        unloadKeys.Clear();
        foreach (var imagePair in ImageCacheDic)
        {
            if (imagePair.Value.holders.Count == 0)
            {
                if (Time.time - imagePair.Value.unusedTime > holdTime)
                {
                    unloadKeys.Add(imagePair.Key);            
                    c++;
                    if (c >= count)
                    {
                        break;
                    }
                }
            }
        }

        foreach (var key in unloadKeys)
        {
            ImageCacheDic[key].Dispose();
            ImageCacheDic.Remove(key);
        }
        unloadKeys.Clear();
    }

    public void UnLoadAllUnusedImage()
    {
        UnLoadUnusedImage(int.MaxValue,float.MinValue);
    }
    

    /// <summary>
    /// 移除对指定图片的持有和加载监听
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="imageUrl">当切换图片，应当移除前一张图片的引用</param>
    /// <param name="success"></param>
    /// <param name="fail"></param>
    public void RemoveLoadWebImageHandler(Object obj, string imageUrl, Action<Texture2D, string> success = null,
        Action<string> fail = null)
    {
        ImageCache imageCache;
        if (ImageCacheDic.TryGetValue(imageUrl,out imageCache))
        {
            imageCache.RemoveHolder(obj);
            imageCache.RemoveSuccessAction(success);
            imageCache.RemoveFailAction(fail);
        }
    }

    /// <summary>
    /// 加载网络图片
    /// </summary>
    /// <param name="obj">引用对象</param>
    /// <param name="imageUrl">图片链接</param>
    /// <param name="success">加载成功回调</param>
    /// <param name="fail">加载失败回调</param>
    public void AddLoadWebImageHandler(Object obj, string imageUrl, Action<Texture2D, string> success = null,
        Action<string> fail = null)
    {
        //WeakReference wr = new WeakReference(obj);
        ImageCache imageCache;
        // 是否有缓存
        if (ImageCacheDic.TryGetValue(imageUrl,out imageCache))
        {
            if (!imageCache.isLoading)
            {
                WeakReference wr = null;
                if (!imageCache.TryGetHolder(obj,out wr))
                {
                    imageCache.AddHolder(obj);
                }
                success?.Invoke(imageCache.image,imageUrl);
            }
            else
            {
                // 正在加载中
                WeakReference wr = null;
                if (!imageCache.TryGetHolder(obj, out wr))
                {
                    imageCache.AddHolder(obj);
                }

                if (success != null)
                {
                    //imageCache.successActions.AddLast(new WeakReference<Action<Texture2D, string>>(success));
                    imageCache.successActions.AddLast(success);
                }

                if (fail != null)
                {
                    //imageCache.failActions.AddLast(new WeakReference<Action<string>>(fail));
                    imageCache.failActions.AddLast(fail);
                }
            }
            return;
        }

        // 异步加载
        imageCache = new ImageCache(imageUrl);
        imageCache.AddHolder(obj);
        ImageCacheDic.Add(imageUrl,imageCache);

        if (success != null)
        {
            //WeakReference<Action<Texture2D, string>> sw = new WeakReference<Action<Texture2D, string>>(success);
            //imageCache.successActions.AddLast(sw);
            imageCache.successActions.AddLast(success);
        }

        if (fail != null)
        {
            //WeakReference<Action<string>> fw = new WeakReference<Action<string>>(fail);
            //imageCache.failActions.AddLast(fw);
            imageCache.failActions.AddLast(fail);
        }

        // 本地有资源,直接加载
        string fileName0 = GetMD5HashFromString(imageUrl);
        string filePath0 = PathForFile(fileName0, IMAGE_CACHE_DIC_NAME);
        if (NativeContains(filePath0))
        {
            //Debug.Log("本地有资源,直接加载-------------------" + filePath0);
            imageCache.isLoading = true;
            StartCoroutine(LoadWebRequest(filePath0, (bytes, tex, url) =>
            {
                ImageCache ic;
                if (ImageCacheDic.TryGetValue(imageUrl,out ic))
                {
                    ic.Success(tex);
                    ic.isLoading = false;
                }
                else
                {
                    // 无人引用的纹理销毁
                    Destroy(tex);
                }
            }, (url) =>
            {
                ImageCache ic;
                if (ImageCacheDic.TryGetValue(imageUrl, out ic))
                {
                    ic.Fail();
                    ic.isLoading = false;
                    ImageCacheDic.Remove(imageUrl);
                }
            }));
            return;
        }

        // 走web加载
        imageCache.isLoading = true;
        LoadImageUseWebClient(imageUrl, (bytes,tex, url) =>
        {
            // 缓存到本地
            string fileName = GetMD5HashFromString(imageUrl);
            string filePath = PathForFile(fileName, IMAGE_CACHE_DIC_NAME);
            SaveNativeFile(bytes,filePath);
            
            ImageCache ic;
            if (ImageCacheDic.TryGetValue(url,out ic))
            {
                ic.Success(tex);
                ic.isLoading = false;
            }
            else
            {
                // 无人引用的纹理销毁
                Destroy(tex);
            }
           
        } , (url) =>
        {
            ImageCache ic;
            if (ImageCacheDic.TryGetValue(url, out ic))
            {
                ic.Fail();
                ic.isLoading = false;
                ImageCacheDic.Remove(url);
            }
        }); 

    }

    IEnumerator LoadWebRequest(string imageUrl, Action<byte[],Texture2D, string> success = null, Action<string> fail = null)
    {
        using (UnityWebRequest uwr = UnityWebRequestTexture.GetTexture(imageUrl))
        {
            uwr.downloadHandler = new DownloadHandlerTexture(true);
            yield return uwr.SendWebRequest();
            
            bool isError = uwr.result == UnityWebRequest.Result.ConnectionError || uwr.result == UnityWebRequest.Result.ProtocolError;
            if (isError)
            {
                fail?.Invoke(imageUrl);
            }
            else
            {
                var texture = DownloadHandlerTexture.GetContent(uwr);
                success?.Invoke(uwr.downloadHandler.data,texture,imageUrl);
            }
        }
    }

    private void LoadImageUseWebClient(string imageUrl, Action<byte[],Texture2D, string> success = null, Action<string> fail = null)
    {
        try
        {
            using (WebClient webClient = new WebClient())
            {
                webClient.DownloadDataCompleted += (sender, args) =>
                {
                    if (args.Error == null && args.Result != null)
                    {
                        Texture2D texture = new Texture2D(0,0);
                        texture.LoadImage(args.Result);
                        success?.Invoke(args.Result,texture,imageUrl);
                    }
                    else
                    {
                        fail?.Invoke(imageUrl);
                    }
                };
                webClient.DownloadDataAsync(new Uri(imageUrl));
            }
        }
        catch (Exception e)
        {
            Debug.LogWarning(e.Message);
            fail?.Invoke(imageUrl);
        }
    }

    private bool NativeContains(string filePath)
    {
        //return false;
        
        return File.Exists(filePath);
    }
    
    
    public byte[] ReadFile(string filePath)
    {
        var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
        fs.Seek(0, SeekOrigin.Begin);
        var binary = new byte[fs.Length];
        fs.Read(binary, 0, binary.Length);
        fs.Close();
        return binary;
    }

    /// <summary>
    /// 获取不同设备下的缓存文件夹
    /// </summary>
    /// <param name="filename"></param>
    /// <returns></returns>
    private string PathForFile(string filename,string dic)
    {
        if (Application.platform == RuntimePlatform.IPhonePlayer)
        {
            string path = Application.persistentDataPath.Substring(0, Application.persistentDataPath.Length - 5);
            path = Path.Combine(path, "Documents");
            path = Path.Combine(path, dic);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            return Path.Combine(path, filename);
        }
        else if (Application.platform == RuntimePlatform.Android)
        {
            string path = Application.persistentDataPath;
            path = Path.Combine(path, dic);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            return Path.Combine(path, filename);
        }
        else
        {
            string path = Application.persistentDataPath;
            path = Path.Combine(path, dic);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            return Path.Combine(path, filename);
        }
    }
    
    
    /// <summary>
    /// 在本地保存文件
    /// </summary>
    /// <param name="bytes"></param>
    /// <param name="path"></param>
    private void SaveNativeFile(byte[] bytes, string path)
    {
        FileStream fs = new FileStream(path, FileMode.Create);
        fs.Write(bytes, 0, bytes.Length);
        fs.Flush();
        fs.Close();
    }


    /// <summary>
    /// 获取md5 hash码
    /// </summary>
    /// <param name="str">待编码字符串</param>
    /// <returns>md5 hash码</returns>
    public static string GetMD5HashFromString(string str, bool large = true)
    {
        string cl = str;
        StringBuilder pwd = new StringBuilder();
        MD5 md5 = MD5.Create(); //实例化一个md5对像
        // 加密后是一个字节类型的数组，这里要注意编码UTF8/Unicode等的选择　
        byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(cl));
        // 通过使用循环，将字节类型的数组转换为字符串，此字符串是常规字符格式化所得
        for (int i = 0; i < s.Length; i++)
        {
            // 将得到的字符串使用十六进制类型格式。格式后的字符是小写的字母，如果使用大写（X）则格式后的字符是大写字符
            if (large)
            {
                pwd.Append(s[i].ToString("X2"));
            }
            else
            {
                pwd.Append(s[i].ToString("x2"));
            }
        }

        return pwd.ToString();
    }

    internal static string URL_PREFIX_HTTP = "http://";
    internal static string URL_PREFIX_HTTPS = "https://";
    internal static string URL_PREFIX_FILE = "file://";
    public static bool IsFromWeb(string value)
    {
        if (!string.IsNullOrEmpty(value)&&(
            value.StartsWith(URL_PREFIX_HTTP) || 
            value.StartsWith(URL_PREFIX_HTTPS) || 
            value.StartsWith(URL_PREFIX_FILE)))
        {
            return true;
        }
        return false;
    }
}
